<template>
    <div class="slide-container" @touchstart.prevent="touchStart($event)"
    @touchmove.prevent="touchMove($event)"
    @touchend.prevent="touchEnd($event)" @transitionend.stop="transitionEnd($event)" ref="slide">
        <!-- <slot :name="sName" v-if="needCustom" v-for="sName in slotNames" ></slot> -->
        <div :class="classList[index]"  :style="slideStyles[index]" v-for="(opt, index) in contents" >
            <img :src="opt.url" alt="" />
        </div>
    </div>
</template>

<script>
const SLIDE_CLASS = 'slide',
SLIDE_MOVE_CLASS = 'move-slide',
attrs = [{
  'left':{ default : 'def1'}
}, {
  'X':{ default : 'def2'}
},{
  'top':{ default : 'def1'}
},{
  'Y':{ default : 'def2'}
}];
export default {
    name: 'slide',
    data () {
        return {
            classList : [], //各滑块class列表
            slideStyles : [], //各滑块样式列表
            defaultOptions : { //默认配置
              slideDir : 'X',
              loop : true,
              bounceDis : 50,
              activeIndex : 0,
              slideEnd : function(e,index){}
            }
        }
    },
    props : {
      'options' : { //配置选项
        type : [ Array, Object ],
        required: true
      }
    },
    created () {
        this.initOptions();
        this.initStates();
        //基于订阅者模式创建简单的任务队列，用管理和延迟任务触发的时机，实现逻辑解耦。
        this.taskList = (() => {
            let tasks = [];
            return {
              //添加回调
              add(callback){
                typeof callback == 'function' && tasks.push(callback);
                return this;
              },
              //触发回调队列，队列会按照添加的顺序依次执行。执行完毕后，便会清空队列。
              exec:(...args) => {
                let result = tasks.length == 1 ? tasks[0](args[0]) : tasks.map((t, i) => t(args[i]));
                tasks = [];
                return result;
              },
              //清空队列
              clear(){
                tasks = [];
                return this;
              },
              //获取队列当前的长度
              size(){
                return tasks.length;
              },
              //弹出指定位置的任务
              pop(index){
                tasks.splice(index, 1);
                return this;
              }
            }
        })();
        //过渡动画管理单例对象
        this._transition = (() => {
            let arr = [], //已经触发/设置过过渡动画的滑块索引列表
            record = {}, //记录已经存入列表的索引，防止列表索引重复添加
            list = this.classList;
            return {
              //触发/设置指定位置或者位置范围内滑块的过渡状态
              fire: (...args) => {
                let value,i,
                _args;
                if(Array.isArray(args[0])){
                    _args = args[0];
                    i = 1;
                }
                i = i || args.length - 1;
                if(i !== 0){
                    if(!_args){
                        _args = args.slice(0, i);
                        _args = this.newArr(..._args);
                    }
                    arr = arr.concat(_args.filter(a => !record[a])
                    .filter(a => record[a] = true));
                }else{
                    _args = arr;
                }
                value = !!args[i];
                if(this.equalsIndex(_args[0], _args[list.length])){
                    _args.shift();
                }
                if(this.equalsIndex(_args[1], _args[list.length + 1])){
                    _args.pop();
                }
                _args.map(this.getLoopIndex.bind(this))
                .forEach(a => {
                    list[a] && (list[a][SLIDE_MOVE_CLASS] = value);
                });
                return _args;
              },
              //清除列表数据
              clear:() =>{
                arr = [];
                record = {};
                return this;
              },
              //检测当前是否有滑块处于过渡状态
              isPlay:() => {
                return arr.map(this.getLoopIndex.bind(this))
                .some(a => list[a] && list[a][SLIDE_MOVE_CLASS]);
              }
            };
        })();
    },
    mounted () {
        this.slideWidth = this.$el.clientWidth;
        this.slideStyles = this.contents.map((_, index) => {
            let initStyle = {
                'left' : '0',
                'top' : '0'
            },
            i = this.activeIndex;
            initStyle[this.def1] = index ==  i ? 0 :
             (index == i + 1 ? this.slideWidth : -this.slideWidth) + 'px';
            return initStyle;
        });
    },
    methods: {
        //初始化配置
        initOptions(){
            //将配置中的的content的值拷贝到默认配置对象的contents属性里
            this.$set(this.defaultOptions, 'contents', this.options instanceof Array
            ? this.options : this.options.content);
            //将默认配置拷贝到Vue实例中
            for(let t in this.defaultOptions){
                this[t] = this.options[t] || this.defaultOptions[t];
            }
            //初始化class列表
            this.classList = this.contents.map(opt => {
                return {
                  [ SLIDE_CLASS ] : true,
                  [ opt.className || '' ] : true,
                  [ SLIDE_MOVE_CLASS ] : false
                };
            });
            //通过slideDir生成相关原生属性名
            this.setAttr(['start', 'page'], this.slideDir == 'X' ? 1 : 3);
            this.setAttr(['', 'offset'], this.slideDir == 'X' ? 0 : 2);
        },
        //初始化状态
        initStates(){
            //设置当前点击的索引为0
            this.touchIndex = 0;
            //设置初始状态
            this.dis = 0;
            this.object = {};
            this.isNextTick = true;
        },
        touchStart(e){
            let index,
            { target, 'changedTouches' : [ touch ] } = e;
            if((index = this.getProxyIndex(target)) == undefined){
                return ;
            }
            const dir = this.slideDir;
            let _trans = this._transition;
            this.taskList.exec(null, [touch, this.$refs.slide.children[index]]);
            this.object[this.start] = this.object[this.start] || touch[this.page];
            this.dis = this.getRangeWithIndex(touch, index);
            this.moveTo(index, this.dis);
            this.touchIndex = index;
        },
        touchMove(e){
            let index,
            { target, 'changedTouches' : [ touch ] } = e;
            if((index = this.getProxyIndex(target)) == undefined){
                return ;
            }
            const dir = this.slideDir,
            start = this.start;
            let dis,
            _trans = this._transition;
            this.taskList.exec(null, [touch, this.$refs.slide.children[index]]);
            if(this.dis * (dis = this.getRangeWithIndex(touch, index)) <= 0){
                this.endUpState(index);
            }
            this.dis = dis;
            this.moveTo(index, dis);
            this.touchIndex = index;

        },
        touchEnd(e){
            let index,
            _trans = this._transition,
            { target, 'changedTouches' : [ touch ] } = e;
            if(_trans.isPlay() ||
              (index = this.getProxyIndex(target)) == undefined){
                return ;
            }
            const dir = this.slideDir;
            let dis = this.dis = this.getRangeWithIndex(touch, index),
            follow = dis > 0 ? index - 1 : index + 1;
            this.endIndex = Math.abs(dis) > this.bounceDis ? follow : index;
            this.touchIndex = index;
            this.scrollTo(this.endIndex);
            this.object[this.start] = 0;
        },
        getLoopIndex(index){
            let length = this.slideStyles.length;
            return this.loop && index < 0 ? length - 1 : this.loop && index > length - 1 ? 0 : index;
        },
        getRangeWithIndex(e, index){
            const dir = this.slideDir;
            let length = this.slideStyles.length,
            dis = e[`page${dir}`] - this.object[`start${dir}`],
            v = dis > 0;
            return !this.loop && Math.abs(dis) > this.bounceDis && (v && index == 0 || !v && index == length - 1)
            ? (v ? 1 : -1 ) * this.bounceDis : dis;
        },
        setRangeByIndex(index, dis){
            let slide = this.slideStyles[ this.getLoopIndex(index) ];
            slide && (slide[this.def1] = dis + 'px');
        },
        getProxyIndex(target){
            let callback = arguments[1],
            [ ...nodes ] = document.querySelectorAll(`.${SLIDE_CLASS}`),
            firstIndex = nodes.findIndex(node => node.contains(target));
            firstIndex = firstIndex == -1 ? undefined : firstIndex;
            if(typeof callback !== 'function'){
                return firstIndex;
            }
            callback.call(null, firstIndex);
        },
        moveTo(index, dis){
            [index, index - 1, index + 1].forEach(i => {
                this.setRangeByIndex(i, dis + (i - index)*this.slideWidth);
            });
        },
        isBoundary(index){
            let length = this.slideStyles.length,
            start = this.loop ? -1 : 0,
            end = this.loop ? length : length - 1;
            return index < start || index > end || typeof index != 'number'  ? true : false;
        },
        scrollTo(index){
            if(this.isBoundary(index) || !this.isNextTick){
                return ;
            }
            let j = this.touchIndex,
            taskList = this.taskList;
            this.isNextTick = false;
            if(taskList.size() !== 0){
                let lastDis = this.$refs.slide.children[j][this.offset],
                iDis = parseInt(lastDis / this.slideWidth);
                this.dis = lastDis % this.slideWidth;
                this.touchIndex = j -= iDis;
                taskList.pop(1).exec();
                this.moveTo(j, this.dis);
            }
            let _trans = this._transition,
            length = this.slideStyles.length,
            start = index < j ? index : this.dis > 0 ? j - 1 : j,
            end = index > j ? index : this.dis > 0 ? j : j + 1,
            indexs = this.newArr(start, end + 1);
            if(this.equalsIndex(indexs[0], indexs[length])){
                if(index == start){
                  start = this.dis > 0 ? j - 1 : j;
                  index = indexs[length];
                }else{
                  end = this.dis > 0 ? j : j + 1;
                  index = start;
                }
            }
            for(let i = start; i <= end; i++ ){
                this.setRangeByIndex(i, this.dis + (i - j) * this.slideWidth);
            }
            this.$nextTick(() => {
                setTimeout(() => {
                  _trans.fire(start, end + 1, true);
                  for(let i = start; i <= end; i++ ){
                      this.setRangeByIndex(i, (i - index) * this.slideWidth);
                  }
                  taskList.add((j) => {
                    _trans.fire(false).forEach(i => {
                        this.setRangeByIndex(i, -this.slideWidth);
                    });
                    _trans.clear();
                  }).add(([touch, slide]) => {
                    this.object[this.start] = touch[this.page] - slide[this.offset];
                  });
                  this.$nextTick(() => this.isNextTick = true);
                },0);
            });
        },
        transitionEnd(e){
            if(this.endIndex !== this.getProxyIndex(e.target)){
                return;
            }
            let _trans = this._transition;
            _trans.fire(false);
            this.endUpState(this.endIndex);
        },
        endUpState(index){
            this.taskList.clear();
            this.dis = 0;
            this.slideEnd(e, this.touchIndex = this.activeIndex = index);
        },
        getAttrName(...attr){
            let isUp = attr[0],
            result = '';
            isUp = typeof isUp == 'boolean' ? attr.shift() : false;
            if(attr.length == 0){
                return '';
            }
            let pre = attr.shift();
            if(Array.isArray(pre)){
                result = this.getAttrName(isUp, ...pre);
            }
            if(typeof pre == 'string'){
                result = isUp ? pre.charAt(0).toUpperCase() + pre.substring(1) : pre;
            }
            return result + this.getAttrName(true, ...attr);
        },
        setAttr(names, ai){
            let attrObject = attrs[ai];
            for(let attr in attrObject){
                let val = attrObject[attr];
                names.forEach(pre => this[pre ? pre : val.default] = this.getAttrName(pre ? [ pre ].concat(attr) : attr));
            }
        },
        equalsIndex(index1, index2){
            if(index1 == index2){
                return;
            }
            return this.getLoopIndex(index1) == index2 && index1 || this.getLoopIndex(index2) == index1 && index2;
        },
        newArr(start, end){
            let list = this.classList,
            keys = Object.keys(list).map((_, a) => parseInt(a));
            if(this.loop){
                keys.push(keys.length);
                keys.unshift(-1);
            }
            end = end || Infinity;
            return keys.filter(a => {
              return a >= start && a < end;
            });
        }

    },
    components: {

    }
}
</script>

<style >
   /*.slide-container{
       position: relative;
       width:100%;
       overflow: hidden;
       height:300px;
       .slide{
           position: absolute;
           width:100%;
           left:0;
           top:0;
           > img{
              width:100%;
          }
       }
       .move-slide{
           transition: all 0.5s;
       }
   }*/


  .slide-container{
      position: relative;
      width:100%;
      overflow: hidden;
      height:300px;
  }
  .slide{
      position: absolute;
      width:100%;
      left:0;
      top:0;
      height:100%;
  }
  .slide > img{
      width:100%;
  }
  .move-slide{
      transition: all 3s;
  }

</style>
